package com.casa.POI.dianping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.XPatherException;

public abstract class FetchPathInfoExecutor<T extends PathInfo> extends ThreadsExecutor {

	private final Object lock = new Object();
	
	protected abstract Class<T> getObjectClass();
	protected abstract T createObject(String url, String name, String path, String parentUrl);

	@SuppressWarnings("unchecked")
	@Override
	protected List<? extends Runnable> getNextBatchWorks() {
		Session s = SessionManager.getInstance().openSession();
		Criteria c = s.createCriteria(getObjectClass());
		c.add(Property.forName("path").isNull());
		c.setMaxResults(ConfigurationManager.getInstance().getBatchSize());
		List<T> items = c.list();
		s.close();
		List<Workitem> works = new ArrayList<FetchPathInfoExecutor<T>.Workitem>();
		for (T item : items) {
			works.add(new Workitem(item));
		}
		return works;
	}
	
	@SuppressWarnings("unchecked")
	private Map<String, String> getUrlNameMappings() {
		Session s = SessionManager.getInstance().openSession();
		Criteria c = s.createCriteria(Shop.class);
		c.setProjection(Projections.projectionList()
				.add(Projections.distinct(Projections.property(getUrlColName())))
				.add(Projections.property(getColName())))
				.add(Property.forName(getUrlColName()).isNotNull());
		List<Object[]> items = c.list();
		s.close();
		Map<String, String> urlNameMappings = new HashMap<String, String>();
		for (Object[] item : items) {
			String urlItem = (String) item[0];
			String nameItem = (String) item[1];
			String[] urls = urlItem.split(":");
			String[] names = nameItem.split(":");
			for (int i = 0; i < urls.length; i++) {
				urlNameMappings.put(urls[i], names[i]);
			}
		}
		return urlNameMappings;
	}
	
	@Override
	public void execute() {
		if (!isUrlNameMappingsPersisted()) {
			persistUrlNameMappings(getUrlNameMappings());
		}
		super.execute();
	}

	private boolean isUrlNameMappingsPersisted() {
		Session s = SessionManager.getInstance().openSession();
		Criteria c = s.createCriteria(getObjectClass());
		c.setMaxResults(1);
		Object result = c.uniqueResult();
		s.close();
		return result != null;
	}
	private void persistUrlNameMappings(Map<String, String> urlNameMappings) {
		if (urlNameMappings.size() > 0) {
			Console.writeLine(StringUtils.EMPTY);
			Session s = SessionManager.getInstance().openSession();
			s.beginTransaction();
			for (Entry<String, String> item : urlNameMappings.entrySet()) {
				T obj = createObject(item.getKey(), item.getValue(), null, null);
				s.saveOrUpdate(obj);
			}
			s.getTransaction().commit();
			s.close();
		}
	}

	private String getUrlColName() {
		return getColName() + "Url";
	}

	private String getColName() {
		return this.getObjectClass().getSimpleName().toLowerCase();
	}
	
	class Workitem implements Runnable {
		private T obj;

		public Workitem(T obj) {
			this.obj = obj;
		}
		
		@Override
		public void run() {
			try {
				List<Pair<String, String>> nodes = fetchNodes();
				synchronized (lock) {
					Session s = SessionManager.getInstance().openSession();
					s.getTransaction().begin();
					printStatus(String.format("fetch %s", obj.getUrl()));
					process(nodes, nodes.size() - 1, s);
					s.getTransaction().commit();
					s.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		private String process(List<Pair<String, String>> nodes, int index, Session s) {
			Pair<String, String> node = nodes.get(index);
			
			String name = node.getLeft();
			String url = node.getRight();
			String path = null;
			String parentUrl = null;
			
			if (index == 0) {
				path = name;
			} else {
				int parentIndex = index - 1;
				String parentPath = process(nodes, parentIndex, s);
				path = new StringBuilder(parentPath).append('-').append(name).toString();
				parentUrl = nodes.get(parentIndex).getRight();
			}
			T obj = createObject(url, name, path, parentUrl);
			s.saveOrUpdate(obj);
			return path;
		}

		private List<Pair<String, String>> fetchNodes() throws IOException, XPatherException {
			TagNode tagNode = SpiderUtil.cleanUrl(obj.getUrl());
			Object[] results = tagNode.evaluateXPath("//div[@class='asideContainer']//a[@class='D arrow']");
			List<Pair<String, String>> nodes = new ArrayList<Pair<String,String>>();
			for (int i = 1; i < results.length; i++) {
				tagNode = (TagNode) results[i];
				Pair<String, String> node = extractPair(tagNode);
				nodes.add(node);
			}
			nodes.add(Pair.of(obj.getName(), obj.getUrl()));
			return nodes;
		}

		private Pair<String, String> extractPair(TagNode node) {
			String text = node.getText().toString();
			text = text.substring(text.lastIndexOf(';') + 1);
			String url = node.getAttributeByName("href");
			return Pair.of(text, url);
		}
	}

}
